Skip to content

Method: lambda$started$6(int, ObservableValue, Number, Number)

1: /*
2: * Copyright © 2021-2023 Fachhochschule für die Wirtschaft (FHDW) Hannover
3: *
4: * This file is part of ipspiel24-VierConnects-gui.
5: *
6: * ipspiel24-VierConnects-gui is free software: you can redistribute it and/or modify it under
7: * the terms of the GNU General Public License as published by the Free Software
8: * Foundation, either version 3 of the License, or (at your option) any later
9: * version.
10: *
11: * ipspiel24-VierConnects-gui is distributed in the hope that it will be useful, but WITHOUT
12: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14: * details.
15: *
16: * You should have received a copy of the GNU General Public License along with
17: * ipspiel24-VierConnects-gui. If not, see <http://www.gnu.org/licenses/>.
18: */
19: package de.fhdw.gaming.ipspiel24.VierConnects.gui.impl;
20:
21: import java.util.LinkedHashMap;
22: import java.util.LinkedHashSet;
23: import java.util.Map;
24: import java.util.Optional;
25: import java.util.Set;
26: import java.util.concurrent.Semaphore;
27: import java.util.concurrent.atomic.AtomicInteger;
28: import java.util.regex.Matcher;
29: import java.util.regex.Pattern;
30: import java.util.stream.IntStream;
31:
32: import de.fhdw.gaming.core.domain.Game;
33: import de.fhdw.gaming.core.domain.Move;
34: import de.fhdw.gaming.core.domain.Player;
35: import de.fhdw.gaming.core.domain.PlayerState;
36: import de.fhdw.gaming.core.domain.State;
37: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsBoard;
38: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsFieldState;
39: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsObserver;
40: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsPlayer;
41: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsPosition;
42: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsState;
43: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsStrategy;
44: import de.fhdw.gaming.ipspiel24.VierConnects.core.moves.VierConnectsMove;
45: import javafx.animation.Animation;
46: import javafx.animation.KeyFrame;
47: import javafx.animation.Timeline;
48: import javafx.application.Platform;
49: import javafx.beans.property.SimpleDoubleProperty;
50: import javafx.beans.property.SimpleObjectProperty;
51: import javafx.beans.value.ObservableValue;
52: import javafx.geometry.HPos;
53: import javafx.geometry.Insets;
54: import javafx.geometry.Pos;
55: import javafx.geometry.VPos;
56: import javafx.scene.control.Label;
57: import javafx.scene.control.Slider;
58: import javafx.scene.layout.Background;
59: import javafx.scene.layout.BackgroundFill;
60: import javafx.scene.layout.Border;
61: import javafx.scene.layout.BorderStroke;
62: import javafx.scene.layout.BorderStrokeStyle;
63: import javafx.scene.layout.ColumnConstraints;
64: import javafx.scene.layout.CornerRadii;
65: import javafx.scene.layout.GridPane;
66: import javafx.scene.layout.HBox;
67: import javafx.scene.layout.Priority;
68: import javafx.scene.layout.RowConstraints;
69: import javafx.scene.layout.VBox;
70: import javafx.scene.paint.Color;
71: import javafx.scene.text.Font;
72: import javafx.scene.text.FontPosture;
73: import javafx.scene.text.FontWeight;
74: import javafx.stage.Screen;
75: import javafx.util.Duration;
76:
77: /**
78: * Displays an VierConnects board.
79: */
80: @SuppressWarnings("PMD.TooManyFields")
81: final class VierConnectsBoardView implements VierConnectsObserver {
82:
83: /**
84: * The initial delay in seconds.
85: */
86: private static final double INITIAL_DELAY = 0.5;
87:
88: /**
89: * The margin used by elements of the state pane.
90: */
91: private static final double STATE_PANE_MARGIN = 40.0;
92:
93: /**
94: * Pattern for extracting the relevant parts of a strategy name.
95: */
96: private static final Pattern STRATEGY_NAME_PATTERN = Pattern.compile("^(VierConnects)?(.*?)(Strategy)?$");
97:
98: /**
99: * The factor to multiply pixels with to receive points.
100: */
101: private final double pixelsToPointsFactor;
102:
103: /**
104: * The field controls.
105: */
106: private final Map<VierConnectsPosition, VierConnectsFieldView> controls;
107:
108: /**
109: * The pane containing everything.
110: */
111: private final HBox rootPane;
112:
113: /**
114: * The pane containing the board and its border.
115: */
116: private final VBox boardPane;
117:
118: /**
119: * The grid pane containing the fields.
120: */
121: private final GridPane fieldPane;
122:
123: /**
124: * The label containing the number of cross marks on the board.
125: */
126: private Label crossMarks;
127:
128: /**
129: * The label containing the number of nought marks on the board.
130: */
131: private Label noughtMarks;
132:
133: /**
134: * The label describing the current game state.
135: */
136: private Label gameStateDescription;
137:
138: /**
139: * The animation of the current game state description (if any).
140: */
141: private Optional<Timeline> gameStateDescriptionAnimation;
142:
143: /**
144: * The delay in milliseconds.
145: */
146: private final AtomicInteger delay;
147:
148: /**
149: * The size of a field control.
150: */
151: private final SimpleDoubleProperty fieldControlSize;
152:
153: /**
154: * The size of various margins.
155: */
156: private final SimpleDoubleProperty margin;
157:
158: /**
159: * The padding used for GridPanes.
160: */
161: private final SimpleObjectProperty<Insets> gridPadding;
162:
163: /**
164: * The font size in pixels.
165: */
166: private final SimpleDoubleProperty fontSizeInPixels;
167:
168: /**
169: * The font used for border labels.
170: */
171: private final SimpleObjectProperty<Font> borderLabelFont;
172:
173: /**
174: * The font used for labels texts in the player's pane.
175: */
176: private final SimpleObjectProperty<Font> labelTextFont;
177:
178: /**
179: * The font used for label values in the player's pane.
180: */
181: private final SimpleObjectProperty<Font> labelValueFont;
182:
183: /**
184: * The font used for displaying the game result.
185: */
186: private final SimpleObjectProperty<Font> gameResultFont;
187:
188: /**
189: * The pane for the top edge.
190: */
191: private HBox topEdge;
192:
193: /**
194: * The pane for the left edge.
195: */
196: private VBox leftEdge;
197:
198: /**
199: * The pane for the right edge.
200: */
201: private VBox rightEdge;
202:
203: /**
204: * The pane for the bottom edge.
205: */
206: private HBox bottomEdge;
207:
208: /**
209: * The last game state.
210: */
211: private Optional<VierConnectsState> lastGameState;
212:
213: /**
214: * The {@link Semaphore} used by {@link VierConnectsBoardEventProviderImpl}.
215: */
216: private final Semaphore semaphore = new Semaphore(0);
217:
218: /**
219: * Creates an {@link VierConnectsBoardView}.
220: *
221: * @param game The game.
222: */
223: VierConnectsBoardView(
224: final Game<VierConnectsPlayer, VierConnectsState, VierConnectsMove, VierConnectsStrategy> game) {
225: this.pixelsToPointsFactor = 72.0 / Screen.getPrimary().getDpi();
226: this.borderLabelFont = new SimpleObjectProperty<>(Font.getDefault());
227: this.labelTextFont = new SimpleObjectProperty<>(Font.getDefault());
228: this.labelValueFont = new SimpleObjectProperty<>(Font.getDefault());
229: this.gameResultFont = new SimpleObjectProperty<>(Font.getDefault());
230:
231: this.fieldControlSize = new SimpleDoubleProperty(0.0);
232: this.margin = new SimpleDoubleProperty(0.0);
233: this.gridPadding = new SimpleObjectProperty<>(Insets.EMPTY);
234: this.fontSizeInPixels = new SimpleDoubleProperty(0.0);
235:
236: this.controls = new LinkedHashMap<>();
237: this.fieldPane = new GridPane();
238: this.boardPane = this.createFieldWithBorderPane();
239:
240: final GridPane statePane = this.createStatePane(game);
241:
242: this.rootPane = new HBox();
243: this.rootPane.getChildren().addAll(this.boardPane, statePane);
244: HBox.setHgrow(this.boardPane, Priority.ALWAYS);
245: HBox.setHgrow(statePane, Priority.ALWAYS);
246: HBox.setMargin(statePane, new Insets(VierConnectsBoardView.STATE_PANE_MARGIN));
247:
248: this.delay = new AtomicInteger((int) (VierConnectsBoardView.INITIAL_DELAY * 1000.0));
249: this.lastGameState = Optional.empty();
250: game.addObserver(this);
251:
252: this.gameStateDescriptionAnimation = Optional.empty();
253: }
254:
255: /**
256: * Creates the pane displaying the board and its border.
257: * <p>
258: * Requires {@link #fieldControlSize} to be valid.
259: */
260: private VBox createFieldWithBorderPane() {
261: final Background background = new Background(
262: new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY));
263:
264: final HBox topLeftCorner = new HBox();
265: topLeftCorner.setBackground(background);
266: topLeftCorner.minHeightProperty().bind(this.fieldControlSize.multiply(0.5));
267: topLeftCorner.minWidthProperty().bind(this.fieldControlSize.multiply(0.5));
268: this.topEdge = new HBox();
269: this.topEdge.setBackground(background);
270: this.topEdge.minHeightProperty().bind(this.fieldControlSize.multiply(0.5));
271: this.topEdge.setAlignment(Pos.CENTER);
272: final HBox topRightCorner = new HBox();
273: topRightCorner.setBackground(background);
274: topRightCorner.minHeightProperty().bind(this.fieldControlSize.multiply(0.5));
275: topRightCorner.minWidthProperty().bind(this.fieldControlSize.multiply(0.5));
276:
277: HBox.setHgrow(topLeftCorner, Priority.NEVER);
278: HBox.setHgrow(this.topEdge, Priority.NEVER);
279: HBox.setHgrow(topRightCorner, Priority.NEVER);
280:
281: this.leftEdge = new VBox();
282: this.leftEdge.setBackground(background);
283: this.leftEdge.minWidthProperty().bind(this.fieldControlSize.multiply(0.5));
284: this.leftEdge.setAlignment(Pos.CENTER);
285: this.rightEdge = new VBox();
286: this.rightEdge.setBackground(background);
287: this.rightEdge.minWidthProperty().bind(this.fieldControlSize.multiply(0.5));
288: this.rightEdge.setAlignment(Pos.CENTER);
289:
290: HBox.setHgrow(this.leftEdge, Priority.NEVER);
291: HBox.setHgrow(this.fieldPane, Priority.NEVER);
292: HBox.setHgrow(this.rightEdge, Priority.NEVER);
293:
294: final HBox bottomLeftCorner = new HBox();
295: bottomLeftCorner.setBackground(background);
296: bottomLeftCorner.minHeightProperty().bind(this.fieldControlSize.multiply(0.5));
297: bottomLeftCorner.minWidthProperty().bind(this.fieldControlSize.multiply(0.5));
298: this.bottomEdge = new HBox();
299: this.bottomEdge.setBackground(background);
300: this.bottomEdge.minHeightProperty().bind(this.fieldControlSize.multiply(0.5));
301: this.bottomEdge.setAlignment(Pos.CENTER);
302: final HBox bottomRightCorner = new HBox();
303: bottomRightCorner.setBackground(background);
304: bottomRightCorner.minHeightProperty().bind(this.fieldControlSize.multiply(0.5));
305: bottomRightCorner.minWidthProperty().bind(this.fieldControlSize.multiply(0.5));
306:
307: HBox.setHgrow(bottomLeftCorner, Priority.NEVER);
308: HBox.setHgrow(this.bottomEdge, Priority.NEVER);
309: HBox.setHgrow(bottomRightCorner, Priority.NEVER);
310:
311: final HBox top = new HBox();
312: top.getChildren().addAll(topLeftCorner, this.topEdge, topRightCorner);
313: final HBox centre = new HBox();
314: centre.getChildren().addAll(this.leftEdge, this.fieldPane, this.rightEdge);
315: final HBox bottom = new HBox();
316: bottom.getChildren().addAll(bottomLeftCorner, this.bottomEdge, bottomRightCorner);
317:
318: VBox.setVgrow(top, Priority.NEVER);
319: VBox.setVgrow(centre, Priority.NEVER);
320: VBox.setVgrow(bottom, Priority.NEVER);
321:
322: final VBox vbox = new VBox();
323: vbox.getChildren().addAll(top, centre, bottom);
324:
325: return vbox;
326: }
327:
328: /**
329: * Creates the pane displaying the game state.
330: *
331: * @param game The game.
332: */
333: private GridPane createStatePane(
334: final Game<VierConnectsPlayer, VierConnectsState, VierConnectsMove, VierConnectsStrategy> game) {
335: final GridPane crossMarksPane = this.createPlayerPane(game, game.getState().getCrossesPlayer());
336: final GridPane noughtMarksPane = this.createPlayerPane(game, game.getState().getNoughtsPlayer());
337:
338: // final Font font = Font.font(null, FontWeight.BOLD, FontPosture.REGULAR, 40.0);
339: this.gameStateDescription = new Label();
340: this.gameStateDescription.fontProperty().bind(this.gameResultFont);
341: this.gameStateDescription.setTextFill(Color.RED);
342:
343: final Label delayLabel = new Label("Delay in seconds: ");
344:
345: final Slider delaySlider = new Slider(0.0, 5.0, VierConnectsBoardView.INITIAL_DELAY);
346: delaySlider.setMajorTickUnit(VierConnectsBoardView.INITIAL_DELAY);
347: delaySlider.setMinorTickCount(4);
348: delaySlider.setBlockIncrement(VierConnectsBoardView.INITIAL_DELAY);
349: delaySlider.setShowTickMarks(true);
350: delaySlider.setShowTickLabels(true);
351: delaySlider.snapToTicksProperty().set(true);
352: delaySlider.valueProperty().addListener(
353: (final ObservableValue<? extends Number> observable, final Number oldValue, final Number newValue) -> {
354: this.delay.set((int) (newValue.doubleValue() * 1000.0));
355: });
356:
357: final HBox delayBox = new HBox(delayLabel, delaySlider);
358: HBox.setHgrow(delayLabel, Priority.NEVER);
359: HBox.setHgrow(delaySlider, Priority.ALWAYS);
360:
361: final GridPane gridPane = new GridPane();
362: gridPane.vgapProperty().bind(this.margin.multiply(2.0));
363:
364: gridPane.addRow(0, crossMarksPane);
365: GridPane.setHalignment(crossMarksPane, HPos.CENTER);
366: GridPane.setHgrow(crossMarksPane, Priority.ALWAYS);
367: GridPane.setVgrow(crossMarksPane, Priority.NEVER);
368:
369: gridPane.addRow(1, noughtMarksPane);
370: GridPane.setHalignment(noughtMarksPane, HPos.CENTER);
371: GridPane.setHgrow(noughtMarksPane, Priority.ALWAYS);
372: GridPane.setVgrow(noughtMarksPane, Priority.NEVER);
373:
374: gridPane.addRow(2, this.gameStateDescription);
375: GridPane.setHalignment(this.gameStateDescription, HPos.CENTER);
376: GridPane.setHgrow(this.gameStateDescription, Priority.ALWAYS);
377: GridPane.setVgrow(this.gameStateDescription, Priority.NEVER);
378:
379: gridPane.addRow(3, delayBox);
380: GridPane.setHalignment(delayBox, HPos.CENTER);
381: GridPane.setHgrow(delayBox, Priority.ALWAYS);
382: GridPane.setVgrow(delayBox, Priority.NEVER);
383:
384: return gridPane;
385: }
386:
387: /**
388: * Returns the root pane.
389: */
390: HBox getNode() {
391: return this.rootPane;
392: }
393:
394: /**
395: * Returns the {@link Semaphore} used for user interaction.
396: */
397: Semaphore getUserInputSemaphore() {
398: return this.semaphore;
399: }
400:
401: /**
402: * Maps a position to the corresponding {@link VierConnectsFieldView}.
403: *
404: * @param position The position.
405: * @return The field view (if it exists).
406: */
407: Optional<VierConnectsFieldView> getFieldView(final VierConnectsPosition position) {
408: return Optional.ofNullable(this.controls.get(position));
409: }
410:
411: /**
412: * Sets the game state.
413: *
414: * @param text The game state.
415: */
416: private void setGameState(final String text) {
417: this.gameStateDescriptionAnimation.ifPresent((final Timeline timeline) -> timeline.stop());
418: this.gameStateDescription.setText(text);
419: if (!text.isEmpty()) {
420: this.gameStateDescriptionAnimation = Optional.of(
421: new Timeline(
422: new KeyFrame(Duration.seconds(0.5), evt -> this.gameStateDescription.setVisible(false)),
423: new KeyFrame(Duration.seconds(1.0), evt -> this.gameStateDescription.setVisible(true))));
424: this.gameStateDescriptionAnimation.get().setCycleCount(Animation.INDEFINITE);
425: this.gameStateDescriptionAnimation.get().play();
426: }
427: }
428:
429: /**
430: * Called if the game has been paused.
431: *
432: * @param game The game.
433: */
434: void gamePaused(final Game<VierConnectsPlayer, VierConnectsState, VierConnectsMove, VierConnectsStrategy> game) {
435: Platform.runLater(() -> this.setGameState("P A U S E D"));
436: }
437:
438: /**
439: * Called if the game has been paused.
440: *
441: * @param game The game.
442: */
443: void gameResumed(final Game<VierConnectsPlayer, VierConnectsState, VierConnectsMove, VierConnectsStrategy> game) {
444: Platform.runLater(() -> this.setGameState(""));
445: }
446:
447: @Override
448: public void started(final Game<?, ?, ?, ?> game, final State<?, ?> state) throws InterruptedException {
449: Platform.runLater(() -> {
450: this.lastGameState = Optional.of((VierConnectsState) state);
451:
452: final VierConnectsBoard board = this.lastGameState.get().getBoard();
453:
454: final int numRows = board.getRowSize();
455: final int numColumns = board.getColumnSize();
456:
457: this.addLabelsToEdge(numRows, numColumns);
458:
459: this.boardPane.widthProperty().addListener(
460: (final ObservableValue<? extends Number> observable, final Number oldValue,
461: final Number newValue) -> {
462: final double width = Math.min(newValue.doubleValue(), this.boardPane.getWidth());
463: this.setBorderAndFieldSize(numRows, width);
464: });
465: this.boardPane.heightProperty().addListener(
466: (final ObservableValue<? extends Number> observable, final Number oldValue,
467: final Number newValue) -> {
468: final double height = Math.min(newValue.doubleValue(), this.boardPane.getWidth());
469: this.setBorderAndFieldSize(numColumns, height);
470: });
471:
472: this.fieldPane.widthProperty().addListener(
473: (final ObservableValue<? extends Number> observable, final Number oldValue,
474: final Number newValue) -> {
475: final double size = Math.min(newValue.doubleValue(), this.fieldPane.getWidth());
476: this.fieldControlSize.set(size / numColumns);
477: });
478: this.fieldPane.heightProperty().addListener(
479: (final ObservableValue<? extends Number> observable, final Number oldValue,
480: final Number newValue) -> {
481: final double size = Math.min(newValue.doubleValue(), this.fieldPane.getHeight());
482: this.fieldControlSize.set(size / numRows);
483: });
484:
485: IntStream.range(0, numRows).forEachOrdered((final int row) -> {
486: IntStream.range(0, numColumns).forEachOrdered((final int column) -> {
487: final VierConnectsPosition position = VierConnectsPosition.of(row, column);
488: final VierConnectsFieldState fieldState = board.getFieldAt(position).getState();
489: final VierConnectsFieldView fieldControl = new VierConnectsFieldView(fieldState);
490: this.fieldPane.add(fieldControl, column, row);
491: this.controls.put(position, fieldControl);
492:
493: fieldControl.prefWidthProperty().bind(this.fieldControlSize);
494: fieldControl.prefHeightProperty().bind(this.fieldControlSize);
495: });
496: });
497: });
498:
499: this.delayNextMove();
500: }
501:
502: /**
503: * Adds the labels to the top and left edges.
504: *
505: * @param numRows The number of rows (and columns) of the board.
506: * @param numColumns The number of rows (and columns) of the board.
507: */
508: private void addLabelsToEdge(final int numRows, final int numColumns) {
509: for (char i = 0; i < numRows; ++i) {
510: final Label leftLabel = new Label(String.valueOf((char) (i + '1')));
511: leftLabel.fontProperty().bind(this.borderLabelFont);
512: leftLabel.setMaxHeight(Double.MAX_VALUE);
513: leftLabel.setAlignment(Pos.CENTER);
514: this.leftEdge.getChildren().add(leftLabel);
515: VBox.setVgrow(leftLabel, Priority.ALWAYS);
516:
517: final Label rightLabel = new Label(String.valueOf((char) (i + '1')));
518: rightLabel.fontProperty().bind(this.borderLabelFont);
519: rightLabel.setMaxHeight(Double.MAX_VALUE);
520: rightLabel.setAlignment(Pos.CENTER);
521: this.rightEdge.getChildren().add(rightLabel);
522: VBox.setVgrow(rightLabel, Priority.ALWAYS);
523: }
524: for (char i = 0; i < numColumns; ++i) {
525: final Label topLabel = new Label(String.valueOf((char) (i + 'A')));
526: topLabel.fontProperty().bind(this.borderLabelFont);
527: topLabel.setMaxWidth(Double.MAX_VALUE);
528: topLabel.setAlignment(Pos.CENTER);
529: this.topEdge.getChildren().add(topLabel);
530: HBox.setHgrow(topLabel, Priority.ALWAYS);
531:
532: final Label bottomLabel = new Label(String.valueOf((char) (i + 'A')));
533: bottomLabel.fontProperty().bind(this.borderLabelFont);
534: bottomLabel.setMaxWidth(Double.MAX_VALUE);
535: bottomLabel.setAlignment(Pos.CENTER);
536: this.bottomEdge.getChildren().add(bottomLabel);
537: HBox.setHgrow(bottomLabel, Priority.ALWAYS);
538: }
539: }
540:
541: /**
542: * Sets the size of the board's fields and border.
543: *
544: * @param numRowsAndColumns The number of rows (and columns) of the board.
545: * @param boardSizeInPixels The width (and height) of the board in pixels.
546: */
547: private void setBorderAndFieldSize(final int numRowsAndColumns, final double boardSizeInPixels) {
548: final double boardSizeWithoutBorders = boardSizeInPixels * numRowsAndColumns / (numRowsAndColumns + 1);
549: this.fieldPane.setPrefSize(boardSizeWithoutBorders, boardSizeWithoutBorders);
550: this.topEdge.setPrefWidth(boardSizeWithoutBorders);
551: this.leftEdge.setPrefHeight(boardSizeWithoutBorders);
552: this.rightEdge.setPrefHeight(boardSizeWithoutBorders);
553: this.bottomEdge.setPrefWidth(boardSizeWithoutBorders);
554:
555: this.fontSizeInPixels.set(this.fieldControlSize.get() * 0.5);
556: final double fontSize = this.fontSizeInPixels.get() * this.pixelsToPointsFactor;
557:
558: final Font fontRegular = Font.font(null, FontWeight.NORMAL, FontPosture.REGULAR, fontSize);
559: final Font fontBold = Font.font(null, FontWeight.BOLD, FontPosture.REGULAR, fontSize);
560:
561: this.borderLabelFont.set(fontRegular);
562: this.labelTextFont.set(fontBold);
563: this.labelValueFont.set(fontRegular);
564: this.gameResultFont.set(fontBold);
565:
566: this.margin.set(this.fontSizeInPixels.get() * 0.25);
567: this.gridPadding.set(new Insets(this.margin.get()));
568: }
569:
570: @Override
571: public void nextPlayersComputed(final Game<?, ?, ?, ?> game, final State<?, ?> state,
572: final Set<? extends Player<?>> players) {
573: // nothing to do
574: }
575:
576: @Override
577: public void illegalPlayerRejected(final Game<?, ?, ?, ?> game, final State<?, ?> state, final Player<?> player) {
578: // nothing to do
579: }
580:
581: @Override
582: public void legalMoveApplied(final Game<?, ?, ?, ?> game, final State<?, ?> state, final Player<?> player,
583: final Move<?, ?> move) throws InterruptedException {
584:
585: Platform.runLater(() -> {
586: final VierConnectsState vierConnectsState = (VierConnectsState) state;
587:
588: this.updateFields(vierConnectsState, VierConnectsFieldState.CROSS, this.crossMarks);
589: this.updateFields(vierConnectsState, VierConnectsFieldState.NOUGHT, this.noughtMarks);
590:
591: this.lastGameState = Optional.of(vierConnectsState);
592: });
593:
594: this.delayNextMove();
595: }
596:
597: /**
598: * Updates the fields and the label for a given field state after a move.
599: *
600: * @param vierConnectsState The game state.
601: * @param fieldState The state of the fields to update ({@link VierConnectsFieldState#CROSS} or
602: * {@link VierConnectsFieldState#NOUGHT}).
603: * @param label The label to update ({@link #crossMarks} or {@link #noughtMarks}).
604: */
605: private void updateFields(final VierConnectsState vierConnectsState, final VierConnectsFieldState fieldState,
606: final Label label) {
607: final Set<VierConnectsPosition> currentFieldsInDesiredState = new LinkedHashSet<>(
608: vierConnectsState.getBoard().getFieldsBeing(fieldState).keySet());
609: label.setText(Integer.toString(currentFieldsInDesiredState.size()));
610: currentFieldsInDesiredState.removeAll(this.lastGameState.get().getBoard().getFieldsBeing(fieldState).keySet());
611: currentFieldsInDesiredState
612: .forEach(
613: (final VierConnectsPosition position) -> this.controls.get(position).setFieldState(fieldState));
614: }
615:
616: @Override
617: public void illegalMoveRejected(final Game<?, ?, ?, ?> game, final State<?, ?> state, final Player<?> player,
618: final Optional<Move<?, ?>> move, final String reason) {
619: // nothing to do
620: }
621:
622: @Override
623: public void overdueMoveRejected(final Game<?, ?, ?, ?> game, final State<?, ?> state, final Player<?> player,
624: final Optional<Move<?, ?>> chosenMove) {
625: // nothing to do
626: }
627:
628: @Override
629: public void playerResigned(final Game<?, ?, ?, ?> game, final State<?, ?> state, final Player<?> player) {
630: // nothing to do
631: }
632:
633: @Override
634: public void playerOvertaken(final Game<?, ?, ?, ?> game, final State<?, ?> state, final Player<?> overtakenPlayer,
635: final Player<?> overtakingPlayer) {
636: // nothing to do
637: }
638:
639: @Override
640: public void finished(final Game<?, ?, ?, ?> game, final State<?, ?> state) {
641: Platform.runLater(() -> {
642: final VierConnectsState vierConnectsState = (VierConnectsState) state;
643: if (vierConnectsState.getCrossesPlayer().getState().equals(PlayerState.WON)) {
644: this.setGameState("RED WINS!");
645: } else if (vierConnectsState.getNoughtsPlayer().getState().equals(PlayerState.WON)) {
646: this.setGameState("YELLOW WINS!");
647: } else if (vierConnectsState.getCrossesPlayer().getState().equals(PlayerState.DRAW)) {
648: this.setGameState("DRAW GAME!");
649: }
650: });
651: }
652:
653: /**
654: * Destroys this object.
655: *
656: * @param game The associated game.
657: */
658: public void destroy(
659: final Game<VierConnectsPlayer, VierConnectsState, VierConnectsMove, VierConnectsStrategy> game) {
660: this.gameStateDescriptionAnimation.ifPresent((final Timeline timeline) -> timeline.stop());
661: this.semaphore.release();
662: game.removeObserver(this);
663: }
664:
665: /**
666: * Delays the execution of the next move.
667: */
668: private void delayNextMove() throws InterruptedException {
669: Thread.sleep(this.delay.get());
670: }
671:
672: /**
673: * Returns a pane displaying the state of one player.
674: *
675: * @param game The game.
676: * @param player The player.
677: * @return The requested pane.
678: */
679: private GridPane createPlayerPane(
680: final Game<VierConnectsPlayer, VierConnectsState, VierConnectsMove, VierConnectsStrategy> game,
681: final VierConnectsPlayer player) {
682: final Label nameLabel = new Label("Name:");
683: nameLabel.fontProperty().bind(this.labelTextFont);
684:
685: final Label nameText = new Label(player.getName());
686: nameText.fontProperty().bind(this.labelValueFont);
687:
688: final Label markLabel = new Label("Color:");
689: markLabel.fontProperty().bind(this.labelTextFont);
690:
691: final Label markText = new Label();
692: markText.fontProperty().bind(this.labelValueFont);
693: if (player.isUsingCrosses()) {
694: markText.setText("Red");
695: } else {
696: markText.setText("Yellow");
697: }
698:
699: final Label strategyLabel = new Label("Strategy:");
700: strategyLabel.fontProperty().bind(this.labelTextFont);
701:
702: final Label strategyText;
703: final String rawName = game.getStrategies().get(player.getName()).toString();
704: final Matcher nameMatcher = VierConnectsBoardView.STRATEGY_NAME_PATTERN.matcher(rawName);
705: if (nameMatcher.matches()) {
706: strategyText = new Label(nameMatcher.group(2));
707: } else {
708: strategyText = new Label(rawName);
709: }
710: strategyText.fontProperty().bind(this.labelValueFont);
711:
712: final Label tokensLabel = new Label("Tokens:");
713: tokensLabel.fontProperty().bind(this.labelTextFont);
714:
715: final Label tokensText = new Label("0");
716: tokensText.fontProperty().bind(this.labelValueFont);
717: if (player.isUsingCrosses()) {
718: this.crossMarks = tokensText;
719: } else {
720: this.noughtMarks = tokensText;
721: }
722:
723: final GridPane gridPane = new GridPane();
724:
725: final ColumnConstraints labelColumnConstraints = new ColumnConstraints();
726: labelColumnConstraints.setHalignment(HPos.RIGHT);
727: labelColumnConstraints.setHgrow(Priority.NEVER);
728: final ColumnConstraints textColumnConstraints = new ColumnConstraints();
729: textColumnConstraints.setHalignment(HPos.LEFT);
730: textColumnConstraints.setHgrow(Priority.ALWAYS);
731: textColumnConstraints.setMaxWidth(Double.MAX_VALUE);
732: gridPane.getColumnConstraints().addAll(labelColumnConstraints, textColumnConstraints);
733:
734: final RowConstraints rowConstraints = new RowConstraints();
735: rowConstraints.setValignment(VPos.TOP);
736: gridPane.getRowConstraints().addAll(rowConstraints, rowConstraints, rowConstraints, rowConstraints);
737:
738: gridPane.addRow(0, nameLabel, nameText);
739: gridPane.addRow(1, markLabel, markText);
740: gridPane.addRow(2, strategyLabel, strategyText);
741: gridPane.addRow(3, tokensLabel, tokensText);
742:
743: gridPane.setBorder(
744: new Border(
745: new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THICK)));
746:
747: gridPane.hgapProperty().bind(this.margin.multiply(2.0));
748: gridPane.paddingProperty().bind(this.gridPadding);
749:
750: return gridPane;
751: }
752: }